Learn in 10 minutes

Learn in 10 minutes

Aprende Python en 10 minutos

Python es un lenguaje de programación de alto nivel e interpretado conocido por su sintaxis concisa y características poderosas. Este tutorial está basado en la última versión de Python 3.13+, ayudándote a aprender Python rápidamente.

1. Escribiendo tu primer programa en Python

Comencemos con un programa simple. Crea un archivo llamado hello.py e ingresa el siguiente código:

print("¡Hola, Mundo!")

Guarda el archivo y ejecuta el siguiente comando en la terminal o línea de comandos:

python hello.py

La salida será:

¡Hola, Mundo!

Este programa simple demuestra la funcionalidad básica de salida de Python. La función print() se utiliza para mostrar información de texto en la consola.

2. Sintaxis básica

La sintaxis de Python es simple y fácil de entender. Python utiliza la indentación para definir bloques de código, a diferencia de otros lenguajes que usan llaves {}.

# Esto es un comentario
print("¡Hola, Mundo!")

Reglas básicas de sintaxis en Python:

  • Indentación: Por defecto, se usan 4 espacios para indicar el nivel de un bloque de código. Por ejemplo, el código dentro de funciones o bucles debe estar indentado.
  • Comentarios: Los comentarios de una sola línea comienzan con #, mientras que los comentarios de varias líneas usan comillas triples """ o '''.
  • Sentencias: Normalmente, una sentencia por línea, no se necesita punto y coma ; al final.
  • Bloques de código: Definidos por indentación, como en if, for, o cuerpos de funciones.

Ejemplo con comentarios de varias líneas:

"""
Este es un comentario de varias líneas,
que abarca múltiples líneas.
"""

La indentación es una característica clave de la sintaxis de Python, utilizada para definir la estructura jerárquica de los bloques de código:

if True:
    print("Esta línea está indentada")
    print("Esta línea también está indentada")
print("Esta línea no está indentada")

3. Variables y tipos de datos

En Python, las variables son contenedores para almacenar datos. Python es un lenguaje de tipado dinámico, lo que significa que no necesitas declarar el tipo de una variable de antemano.

Reglas básicas para nombrar variables:

  • Los nombres de las variables solo pueden contener letras, números y guiones bajos.
  • Los nombres de las variables no pueden comenzar con un número.
  • Los nombres de las variables son sensibles a mayúsculas y minúsculas.
  • Las palabras clave de Python no pueden usarse como nombres de variables.

Los tipos de las variables se determinan por el valor asignado. Los principales tipos de datos básicos de Python son:

  • Entero (int): Por ejemplo, 42 o -10, sin límite de tamaño.
  • Flotante (float): Por ejemplo, 3.14 o 2.5e3 (notación científica).
  • Cadena (str): Por ejemplo, "hola" o 'mundo', usando comillas simples o dobles.
  • Booleano (bool): True o False.
  • Ninguno (None): Representado por None, indica nulo o sin valor.

Python soporta sugerencias de tipo para mejorar la legibilidad del código, usadas para verificación estática y soporte en IDE, sin afectar el comportamiento en tiempo de ejecución:

nombre: str = "Alicia"
edad: int = 25

3.1 Tipos numéricos (Number)

Python soporta tres tipos numéricos: entero (int), flotante (float) y complejo (complex).

# Entero
edad = 25
poblacion = 1000000

# Flotante
temperatura = 36.5
pi = 3.14159

# Complejo
numero_complejo = 3 + 4j

3.2 Cadena (String)

Las cadenas son secuencias de caracteres, encerradas en comillas simples o dobles.

comilla_simple = 'Cadena con comilla simple'
comilla_doble = "Cadena con comilla doble"
multilinea = """Esta es una
cadena multilínea"""

Operaciones con cadenas:

texto = "Programación en Python"
print(len(texto))        # Longitud de la cadena
print(texto.upper())     # Convertir a mayúsculas
print(texto.lower())     # Convertir a minúsculas
print(texto[0])          # Acceder al primer carácter
print(texto[2:6])        # Segmentación de la cadena

3.3 Tipo booleano (Boolean)

El tipo booleano tiene dos valores: True y False.

esta_activo = True
esta_completado = False

# Operaciones booleanas
resultado1 = True and False  # False
resultado2 = True or False   # True
resultado3 = not True        # False

3.4 Tipo Ninguno (None)

None representa un estado nulo o sin valor.

valor = None

if valor is None:
    print("El valor es nulo")

4. Estructuras de datos

Python proporciona varias estructuras de datos integradas para almacenar y manipular datos. A continuación, se presentan las estructuras de datos más comunes y su uso.

4.1 Lista (List)

Una lista es una colección ordenada y mutable. Puedes agregar, eliminar o modificar elementos en una lista. Las listas se definen usando corchetes [].

numeros = [1, 2, 3, 4, 5]
numeros.append(6)      # Agregar elemento
numeros.insert(0, 0)   # Insertar en una posición específica
numeros.remove(3)      # Eliminar un valor específico
numeros[0] = 10        # Modificar elemento
print(numeros)         # [10, 2, 4, 5, 6]

Segmentación de listas para acceder a sublistas:

numeros = [10, 20, 30, 40, 50]
print(numeros[1:4])    # Salida: [20, 30, 40]
print(numeros[:3])     # Salida: [10, 20, 30]
print(numeros[-2:])    # Salida: [40, 50]

Comprensión de listas:

cuadrados = [x**2 for x in range(5)]
print(cuadrados)         # [0, 1, 4, 9, 16]

cuadrados_pares = [x**2 for x in range(10) if x % 2 == 0]
print(cuadrados_pares)    # [0, 4, 16, 36, 64]

4.2 Tupla (Tuple)

Una tupla es una colección ordenada pero inmutable. Una vez creada, sus elementos no pueden modificarse. Las tuplas se definen usando paréntesis (). Debido a su inmutabilidad, las tuplas son generalmente más rápidas que las listas y pueden usarse como claves de diccionarios.

punto = (10, 20)
x, y = punto  # Desempaquetado
print(x, y)   # Salida: 10 20

Tuplas de un solo elemento requieren una coma:

tupla_unica = (42,)

4.3 Diccionario (Dict)

Un diccionario es una colección no ordenada (ordenada en Python 3.7+) de pares clave-valor. Cada clave es única y está asociada con un valor. Los diccionarios se definen usando llaves {}.

estudiante = {
    "nombre": "Juan",
    "edad": 20,
    "carrera": "Ciencias de la Computación"
}

# Accediendo y modificando diccionario
print(estudiante["nombre"])
estudiante["edad"] = 21
estudiante["promedio"] = 3.8

# Acceso seguro
print(estudiante.get("telefono", "No proporcionado"))

# Iterando sobre diccionario
for clave, valor in estudiante.items():
    print(f"{clave}: {valor}")

Comprensión de diccionarios:

# Crear un diccionario de cuadrados
diccionario_cuadrados = {x: x**2 for x in range(5)}
print(diccionario_cuadrados)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Comprensión de diccionario condicional
diccionario_cuadrados_pares = {x: x**2 for x in range(10) if x % 2 == 0}
print(diccionario_cuadrados_pares)  # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

4.4 Conjunto (Set)

Un conjunto es una colección no ordenada sin elementos duplicados. Se define usando llaves {} o set().

# Crear un conjunto
frutas = {"manzana", "plátano", "naranja"}
numeros = set([1, 2, 3, 3, 4, 4, 5])  # Eliminación automática de duplicados
print(numeros)  # {1, 2, 3, 4, 5}

# Operaciones con conjuntos
conjunto1 = {1, 2, 3, 4}
conjunto2 = {3, 4, 5, 6}

print(conjunto1 | conjunto2)  # Unión: {1, 2, 3, 4, 5, 6}
print(conjunto1 & conjunto2)  # Intersección: {3, 4}
print(conjunto1 - conjunto2)  # Diferencia: {1, 2}
print(conjunto1 ^ conjunto2)  # Diferencia simétrica: {1, 2, 5, 6}

5. Operaciones y operadores

Python ofrece un conjunto rico de operadores para diversos cálculos y comparaciones, incluyendo operadores aritméticos, de comparación, lógicos, de bits e identidad.

  • Operadores aritméticos: +, -, *, /, // (división entera), % (módulo), ** (exponenciación).
  • Operadores de comparación: ==, !=, >, <, >=, <=.
  • Operadores lógicos: and, or, not.
  • Operadores de pertenencia: in, not in.
  • Operadores de identidad: is, is not.

5.1 Operadores aritméticos

Los operadores aritméticos se utilizan para operaciones matemáticas. La precedencia de los operadores sigue las reglas matemáticas (por ejemplo, ** tiene mayor precedencia que + o -). Los paréntesis () pueden usarse para cambiar la precedencia.

a, b = 10, 3

print(f"Suma: {a + b}")      # 13
print(f"Resta: {a - b}")     # 7
print(f"Multiplicación: {a * b}")  # 30
print(f"División: {a / b}")      # 3.333...
print(f"División entera: {a // b}")  # 3
print(f"Módulo: {a % b}")      # 1
print(f"Exponenciación: {a ** b}")  # 1000

5.2 Operadores de comparación

Los operadores de comparación comparan dos valores y devuelven un booleano (True o False).

x, y = 5, 10

print(f"Igual: {x == y}")     # False
print(f"No igual: {x != y}")  # True
print(f"Mayor que: {x > y}")  # False
print(f"Menor que: {x < y}")  # True
print(f"Mayor o igual: {x >= y}")  # False
print(f"Menor o igual: {x <= y}")  # True

5.3 Operadores lógicos

Los operadores lógicos combinan o manipulan valores booleanos (True o False), típicamente usados en sentencias condicionales.

a, b = True, False

print(f"Operación AND: {a and b}")  # False
print(f"Operación OR: {a or b}")    # True
print(f"Operación NOT: {not a}")    # False

5.4 Operadores de identidad

El operador is compara la identidad de dos objetos, verificando si hacen referencia a la misma dirección de memoria (no solo valores iguales). Se diferencia de ==, que compara contenido.

lista1 = [1, 2, 3]
lista2 = [1, 2, 3]
lista3 = lista1

print(f"lista1 is lista2: {lista1 is lista2}")    # False
print(f"lista1 is lista3: {lista1 is lista3}")    # True
print(f"lista1 == lista2: {lista1 == lista2}")    # True

5.5 Operadores de pertenencia

Los operadores de pertenencia verifican si un valor es miembro de una secuencia (por ejemplo, lista, tupla, cadena, conjunto), devolviendo un booleano.

frutas = ["manzana", "plátano", "naranja"]

print(f"'manzana' in frutas: {'manzana' in frutas}")        # True
print(f"'uva' not in frutas: {'uva' not in frutas}")  # True

6. Flujo de control

Python proporciona varias sentencias de flujo de control para gestionar el orden de ejecución de un programa.

6.1 Sentencias if

La sentencia if evalúa una condición y ejecuta su bloque si la condición es True. Usa elif y else para condiciones complejas.

edad = 20
if edad >= 18:
    print("Adulto")
elif edad >= 13:
    print("Adolescente")
else:
    print("Niño")

6.2 Bucles for

El bucle for itera sobre un objeto iterable (por ejemplo, lista, tupla, cadena o rango).

# Iterando sobre una lista
frutas = ["manzana", "plátano", "cereza"]
for fruta in frutas:
    print(fruta)

# Usando range() para bucles
for i in range(5):
    print(i)  # Salida: 0, 1, 2, 3, 4

6.3 Bucles while

El bucle while continúa ejecutando un bloque mientras su condición siga siendo True.

contador = 0
while contador < 5:
    print(contador)
    contador += 1
  • break y continue: break sale del bucle, continue omite la iteración actual.
for i in range(10):
    if i == 5:
        break
    if i % 2 == 0:
        continue
    print(i)  # Salida: 1, 3

6.4 Sentencias match

Introducidas en Python 3.10, las sentencias match proporcionan un poderoso emparejamiento de patrones estructurales, actuando como una alternativa avanzada a las cadenas if/elif/else.

estado_http = 200

match estado_http:
    case 200 | 201:
        print("Éxito")
    case 404:
        print("No encontrado")
    case 500:
        print("Error del servidor")
    case _:  # Comodín, coincide con cualquier otro caso
        print("Estado desconocido")

7. Entrada y salida

7.1 Entrada y salida básicas

Usa la función input() para obtener entrada del usuario y la función print() para mostrar información.

# Obteniendo entrada del usuario
nombre = input("Por favor, introduce tu nombre: ")
edad = int(input("Por favor, introduce tu edad: "))

# Mostrando información
print("Bienvenido", nombre)
print("Tienes", edad, "años")

7.2 Salida formateada (f-strings)

Introducidas en Python 3.6+, las f-strings (literales de cadena formateados) son una forma conveniente y poderosa de formatear cadenas. Prefija una cadena con f o F e inserta variables o expresiones en llaves {}.

usuario = "Alicia"
articulos = 3
costo_total = 45.5

# Usando f-string para un mensaje formateado
mensaje = f"El usuario {usuario} compró {articulos} artículos por ${costo_total:.2f}."
print(mensaje)

# Expresiones en f-strings
print(f"2 + 3 es igual a {2 + 3}")

Expresiones y llamadas a funciones en f-strings:

ancho = 10
alto = 5

# Usando expresiones en f-strings
area = f"Área del rectángulo: {ancho * alto}"
print(area)

# Llamando funciones
texto = "python"
formateado = f"Mayúsculas: {texto.upper()}, Longitud: {len(texto)}"
print(formateado)

Opciones de formato en f-strings:

pi = 3.14159265359
numero_grande = 1234567

# Formato de números
print(f"Pi (2 decimales): {pi:.2f}")
print(f"Pi (4 decimales): {pi:.4f}")
print(f"Número grande (miles): {numero_grande:,}")
print(f"Porcentaje: {0.85:.1%}")

# Alineación de cadenas
nombre = "Python"
print(f"Alineación a la izquierda: '{nombre:<10}'")
print(f"Alineación a la derecha: '{nombre:>10}'")
print(f"Alineación al centro: '{nombre:^10}'")

Formateo de fecha y hora con f-strings:

from datetime import datetime

ahora = datetime.now()
print(f"Hora actual: {ahora}")
print(f"Hora formateada: {ahora:%Y-%m-%d %H:%M:%S}")
print(f"Solo fecha: {ahora:%Y-%m-%d}")

9. Funciones

Las funciones en Python son bloques de código reutilizables para tareas específicas, definidas usando la palabra clave def, con soporte para parámetros predeterminados, argumentos variables y argumentos de palabra clave.

Definición básica de una función:

def saludar(nombre):
    """Función de saludo"""
    return f"¡Hola, {nombre}!"

# Llamando a la función
mensaje = saludar("Juan")
print(mensaje)

Argumentos de palabra clave:

Los argumentos de palabra clave se pasan usando la sintaxis nombre_parámetro=valor.

def saludar(nombre, saludo="Hola"):
    return f"{saludo}, {nombre}!"

print(saludar("Alicia"))          # Salida: Hola, Alicia!
print(saludar("Bob", "Hola"))     # Salida: Hola, Bob!

Argumentos variables:

Los argumentos variables permiten que las funciones acepten un número arbitrario de argumentos, ya sean posicionales (*args) o de palabra clave (**kwargs).

# Argumentos posicionales variables (*args)
def sumar_numeros(*args):
    return sum(args)

print(sumar_numeros(1, 2, 3, 4))  # Salida: 10

# Argumentos de palabra clave variables (**kwargs)
def imprimir_kwargs(**kwargs):
    for clave, valor in kwargs.items():
        print(f"{clave}: {valor}")

imprimir_kwargs(nombre="Alicia", edad=25, ciudad="Beijing")

Anotaciones de funciones:

Las anotaciones de funciones agregan metadatos descriptivos a los parámetros y valores de retorno de las funciones, mejorando la legibilidad y la documentación.

def calcular_area(largo: float, ancho: float) -> float:
    """Calcular el área de un rectángulo"""
    return largo * ancho

def procesar_usuario(nombre: str, edad: int, activo: bool = True) -> dict:
    """Procesar información del usuario"""
    return {
        "nombre": nombre,
        "edad": edad,
        "activo": activo
    }

10. Expresiones lambda

Las expresiones lambda crean funciones anónimas, proporcionando una forma concisa de definir funciones pequeñas.

cuadrado = lambda x: x ** 2
print(cuadrado(5))  # Salida: 25

Comúnmente usadas con funciones de orden superior como map o filter:

numeros = [1, 2, 3, 4]
cuadrados = list(map(lambda x: x ** 2, numeros))
print(cuadrados)  # Salida: [1, 4, 9, 16]

11. Clases y objetos

Python soporta la programación orientada a objetos usando la palabra clave class:

class Persona:
    """Clase Persona"""
    
    def __init__(self, nombre, edad):
        """Constructor"""
        self.nombre = nombre
        self.edad = edad
    
    def presentarse(self):
        """Método de presentación"""
        return f"Soy {self.nombre}, tengo {self.edad} años"
    
    def cumplir_anios(self):
        """Método de cumpleaños"""
        self.edad += 1
        return f"{self.nombre} tuvo un cumpleaños, ahora tiene {self.edad} años"

# Creando objetos
persona1 = Persona("Juan", 25)
persona2 = Persona("Jane", 30)

print(persona1.presentarse())
print(persona2.cumplir_anios())

11.1 Atributos de clase e instancia

En Python, los atributos de clase y los atributos de instancia son dos tipos de atributos para almacenar datos en clases y objetos.

  • Atributos de clase: Definidos fuera de los métodos, pertenecen a la clase misma y se comparten entre todas las instancias.
  • Atributos de instancia: Definidos en métodos (generalmente __init__), están vinculados a instancias específicas a través de self.
class Estudiante:
    # Atributos de clase
    universidad = "Universidad de Stanford"
    conteo_estudiantes = 0
    
    def __init__(self, nombre, carrera):
        # Atributos de instancia
        self.nombre = nombre
        self.carrera = carrera
        Estudiante.conteo_estudiantes += 1
    
    @classmethod
    def obtener_conteo_estudiantes(cls):
        """Método de clase"""
        return cls.conteo_estudiantes
    
    @staticmethod
    def es_edad_valida(edad):
        """Método estático"""
        return 0 < edad < 150

# Ejemplo de uso
estudiante1 = Estudiante("Juan", "Ciencias de la Computación")
estudiante2 = Estudiante("Jane", "Matemáticas")

print(f"Universidad: {Estudiante.universidad}")
print(f"Total de estudiantes: {Estudiante.obtener_conteo_estudiantes()}")
print(f"Edad válida: {Estudiante.es_edad_valida(20)}")

11.2 Herencia de clases

La herencia permite que una clase (subclase) herede atributos y métodos de otra clase (clase padre), habilitando la reutilización y extensión del código.

class Animal:
    def __init__(self, nombre, especie):
        self.nombre = nombre
        self.especie = especie
    
    def hacer_sonido(self):
        return f"{self.nombre} hace un sonido"
    
    def informacion(self):
        return f"{self.nombre} es un {self.especie}"

class Perro(Animal):
    def __init__(self, nombre, raza):
        super().__init__(nombre, "Perro")
        self.raza = raza
    
    def hacer_sonido(self):
        return f"{self.nombre} ladra"
    
    def buscar(self):
        return f"{self.nombre} busca la pelota"

class Gato(Animal):
    def __init__(self, nombre, color):
        super().__init__(nombre, "Gato")
        self.color = color
    
    def hacer_sonido(self):
        return f"{self.nombre} maúlla"
    
    def trepar(self):
        return f"{self.nombre} trepa un árbol"

# Usando herencia
perro = Perro("Buddy", "Golden Retriever")
gato = Gato("Mimi", "Naranja")

print(perro.informacion())
print(perro.hacer_sonido())
print(perro.buscar())

print(gato.informacion())
print(gato.hacer_sonido())
print(gato.trepar())

11.3 Métodos especiales (Métodos mágicos)

Los métodos especiales (o métodos mágicos) son métodos con doble guion bajo que definen comportamientos específicos, llamados automáticamente por Python en ciertos escenarios.

class Rectangulo:
    def __init__(self, ancho, alto):
        self.ancho = ancho
        self.alto = alto
    
    def __str__(self):
        """Representación en cadena"""
        return f"Rectángulo({self.ancho}x{self.alto})"
    
    def __repr__(self):
        """Representación oficial en cadena"""
        return f"Rectangulo(ancho={self.ancho}, alto={self.alto})"
    
    def __eq__(self, otro):
        """Comparación de igualdad"""
        if isinstance(otro, Rectangulo):
            return self.ancho == otro.ancho and self.alto == otro.alto
        return False
    
    def __lt__(self, otro):
        """Comparación menor que (por área)"""
        if isinstance(otro, Rectangulo):
            return self.area() < otro.area()
        return NotImplemented
    
    def __add__(self, otro):
        """Operación de suma"""
        if isinstance(otro, Rectangulo):
            return Rectangulo(self.ancho + otro.ancho, self.alto + otro.alto)
        return NotImplemented
    
    def area(self):
        """Calcular área"""
        return self.ancho * self.alto

# Usando métodos especiales
rect1 = Rectangulo(3, 4)
rect2 = Rectangulo(5, 6)
rect3 = Rectangulo(3, 4)

print(rect1)           # Rectángulo(3x4)
print(repr(rect1))     # Rectangulo(ancho=3, alto=4)
print(rect1 == rect3)  # True
print(rect1 < rect2)   # True
print(rect1 + rect2)   # Rectángulo(8x10)

12. Gestores de contexto

Los gestores de contexto aseguran la adquisición y liberación adecuada de recursos, comúnmente usados con la sentencia with para la gestión de recursos, como archivos o conexiones a bases de datos.

12.1 Usando gestores de contexto

Las operaciones con archivos son un caso común para los gestores de contexto:

with open("ejemplo.txt", "r") as archivo:
    contenido = archivo.read()
    print(contenido)

La sentencia with asegura que el archivo se cierre automáticamente después de las operaciones.

12.2 Gestores de contexto personalizados

Crea gestores de contexto personalizados definiendo los métodos __enter__ y __exit__:

class ConexionBaseDatos:
    def __init__(self, nombre_base_datos):
        self.nombre_base_datos = nombre_base_datos
        self.conexion = None
    
    def __enter__(self):
        """Llamado al entrar al contexto"""
        print(f"Conectando a la base de datos: {self.nombre_base_datos}")
        self.conexion = f"Conexión a {self.nombre_base_datos}"
        return self.conexion
    
    def __exit__(self, tipo_excepcion, valor_excepcion, rastreo):
        """Llamado al salir del contexto"""
        print(f"Cerrando conexión a la base de datos: {self.nombre_base_datos}")
        if tipo_excepcion:
            print(f"Ocurrió una excepción: {tipo_excepcion.__name__}: {valor_excepcion}")
        self.conexion = None
        return False  # No suprimir excepciones

# Usando gestor de contexto personalizado
with ConexionBaseDatos("base_datos_usuarios") as conn:
    print(f"Usando conexión: {conn}")
    print("Realizando operaciones en la base de datos...")

Usando el módulo contextlib para crear gestores de contexto:

from contextlib import contextmanager
import time

@contextmanager
def temporizador(nombre_operacion):
    """Gestor de contexto de temporizador"""
    print(f"Iniciando {nombre_operacion}")
    tiempo_inicio = time.time()
    try:
        yield
    finally:
        tiempo_fin = time.time()
        print(f"{nombre_operacion} completada en {tiempo_fin - tiempo_inicio:.2f} segundos")

# Usando gestor de contexto basado en decorador
with temporizador("procesamiento de datos"):
    # Simular operación que consume tiempo
    time.sleep(1)
    print("Procesando datos...")

13. Manejo de excepciones

El manejo de excepciones asegura la robustez del programa, usando try, except, else y finally para gestionar excepciones.

try:
    resultado = 10 / 0
except ZeroDivisionError:
    print("¡No se puede dividir por cero!")
else:
    print("División exitosa")
finally:
    print("Esto siempre se ejecuta")

Salida:

¡No se puede dividir por cero!
Esto siempre se ejecuta

14. Operaciones con archivos

Python proporciona métodos simples para leer y escribir archivos, típicamente usados con gestores de contexto.

14.1 Leyendo archivos

Leyendo el contenido de un archivo de texto:

with open("ejemplo.txt", "r") as archivo:
    contenido = archivo.read()
    print(contenido)

Leyendo línea por línea:

with open("ejemplo.txt", "r") as archivo:
    for linea in archivo:
        print(linea.strip())

14.2 Escribiendo archivos

Escribiendo en un archivo de texto:

with open("salida.txt", "w") as archivo:
    archivo.write("¡Hola, Python!\n")

Agregando contenido:

with open("salida.txt", "a") as archivo:
    archivo.write("Agregando nuevo contenido.\n")

15. Módulos y paquetes

Los módulos son archivos que contienen código Python, y los paquetes son directorios que contienen múltiples módulos. Importa módulos usando import:

import math
print(math.sqrt(16))  # Salida: 4.0

Ejemplo de módulo personalizado (suponiendo que el archivo se llama mimodulo.py):

# mimodulo.py
def decir_hola():
    return "¡Hola desde el módulo!"

Importando y usando:

import mimodulo
print(mimodulo.decir_hola())  # Salida: ¡Hola desde el módulo!

16. Alcance y espacio de nombres

16.1 Alcance

El alcance define la región donde una variable es accesible. Python sigue la regla LEGB para la búsqueda de variables:

  • L (Local): Dentro de una función o método de clase.
  • E (Enclosing): En la función externa de una función anidada (cierre).
  • G (Global): En el nivel del módulo.
  • B (Built-in): Funciones y excepciones integradas como print() o len().
x = "x global"

def funcion_externa():
    x = "x envolvente"
    def funcion_interna():
        x = "x local"
        print(x) # Accede al alcance local x
    funcion_interna()
    print(x) # Accede al alcance envolvente x

funcion_externa()
print(x) # Accede al alcance global x

Usando global o nonlocal para modificar variables de alcance:

x = "global"
def modificar_global():
    global x
    x = "modificado"
modificar_global()
print(x)  # Salida: modificado

16.2 Espacio de nombres

Un espacio de nombres es un mapeo de nombres a objetos, como un diccionario donde las claves son nombres de variables y los valores son los objetos.

Cada módulo, función y clase tiene su propio espacio de nombres, evitando conflictos de nombres. Usa globals() y locals() para inspeccionar espacios de nombres.

una_variable = 10

def alguna_funcion():
    variable_b = 20
    # locals() devuelve el diccionario del espacio de nombres local actual
    print(f"Locales: {locals()}")

print(f"Globales: {globals().keys()}") # Imprime las claves del espacio de nombres global
alguna_funcion()

17. Generadores

Los generadores son iteradores especiales que generan valores bajo demanda, en lugar de crear todos los valores de una vez, lo que los hace eficientes en memoria para conjuntos de datos grandes.

Los generadores usan la palabra clave yield para devolver valores de forma perezosa:

def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

for num in fibonacci(5):
    print(num)  # Salida: 0, 1, 1, 2, 3

18. Multihilo

El multihilo permite que un programa realice múltiples operaciones de manera concurrente, útil para tareas limitadas por entrada/salida, como solicitudes de red o operaciones con archivos.

El módulo threading de Python proporciona herramientas para crear y gestionar hilos. Debido al Global Interpreter Lock (GIL), el multihilo no logra un verdadero paralelismo de CPU en un solo proceso, pero mejora significativamente el rendimiento para tareas limitadas por entrada/salida.

import threading
import time

def trabajador(nombre_hilo):
    print(f"Hilo {nombre_hilo} iniciando...")
    time.sleep(2) # Simular operación que consume tiempo
    print(f"Hilo {nombre_hilo} finalizado.")

# Crear hilos
hilo1 = threading.Thread(target=trabajador, args=("A",))
hilo2 = threading.Thread(target=trabajador, args=("B",))

# Iniciar hilos
hilo1.start()
hilo2.start()

# Esperar a que todos los hilos terminen
hilo1.join()
hilo2.join()

print("Todos los hilos completados.")

19. Programación asíncrona

La programación asíncrona es ideal para escenarios de alta entrada/salida y alta concurrencia, usando un bucle de eventos para gestionar tareas en lugar de hilos.

Python soporta la programación asíncrona a través de la biblioteca asyncio y la sintaxis async/await.

  • async def: Define una corrutina.
  • await: Pausa la ejecución de la corrutina, esperando a que un objeto esperable se complete.
import asyncio

async def decir_hola():
    print("Hola")
    await asyncio.sleep(1) # Sueño no bloqueante, simulando entrada/salida
    print("Mundo")

async def principal():
    # Crear y esperar una tarea
    await asyncio.create_task(decir_hola())

# Ejecutar la corrutina principal
asyncio.run(principal())

Salida:

Hola
Mundo